﻿using Dycox.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

namespace Dycox.Reporting
{
    public class DataSheet : BlockElement
    {
        public DataSheet()
        {

        }

        private List<DataSheetColumn> _columns;
        [XmlArray("columns"), XmlArrayItem("column", typeof(DataSheetColumn))]
        public List<DataSheetColumn> Columns
        {
            get
            {
                if (_columns == null)
                    _columns = new List<DataSheetColumn>(10);
                return _columns;
            }
        }

        private string _dataMember;

        [XmlAttribute("dataMember")]
        public string DataMember
        {
            get { return _dataMember; }
            set { _dataMember = value; }
        }

        private SizeF _standardCellSize;

        [XmlAttribute("standardCellSize")]
        public SizeF StandardCellSize
        {
            get { return _standardCellSize; }
            set { _standardCellSize = value; }
        }

        private float _cellPadding;
        [XmlAttribute("cellPadding")]
        public float CellPadding
        {
            get { return _cellPadding; }
            set { _cellPadding = value; }
        }

        private int _preferredRowCount;
        [XmlAttribute("rowCount")]
        public int PreferredRowCount
        {
            get { return _preferredRowCount; }
            set { _preferredRowCount = value; }
        }


        object _dataObject;
        Table _table;

        protected internal override void Render(RenderingContext context)
        {
            GetTable(context).Render(context);
        }

        protected internal override System.Drawing.RectangleF GetBounds(RenderingContext context)
        {
            return GetTable(context).GetBounds(context);
        }

        private Table GetTable(RenderingContext context)
        {
            //if (context.DataObject != _dataObject)
            {
                _dataObject = context.DataObject;
                Table table = CreateTable();
                if (_table != null)
                    _table.Parent = null;
                _table = table;
                _table.Parent = this.Parent;
            }

            return _table;
        }

        private Table CreateTable()
        {
            Table table = new Table();
            table.Location = this.Location;
            if (!this.AutoSize)
                table.Size = this.Size;
            table.Style.OverrideBy(this.Style);
            table.StandardCellSize = _standardCellSize;
            table.CellPadding = _cellPadding;
            
            InitializeTable(table);

            return table;
        }

        private IEnumerable GetDataSource()
        {
            if (_dataObject == null)
                throw new InvalidOperationException();

            object data = _dataObject;

            if (!string.IsNullOrEmpty(_dataMember))
                data = Utils.DataBinderUtil.Eval(data, _dataMember);

            IEnumerable coll = data as IEnumerable;

            if (coll == null)
                throw new InvalidOperationException("Not a valid data member.");

            return coll;
        }

        private void InitializeTable(Table table)
        {
            TableRow hrow0 = new TableRow(), hrow1, frow = null;
            int hcount = 0, gcount = 0;
            List<AggregatableCell> aggcells = new List<AggregatableCell>(Columns.Count);
            TableCell fcell = null;

            foreach (var dscol in Columns)
            {
                int colindex = table.Columns.Count;

                var header = dscol.Header;
                TableCell cell = new TableCell();

                if (header != null)
                {
                    hcount++;
                    if (!string.IsNullOrEmpty(header.Group)) gcount++;
                    cell.Style = dscol.GetHeaderStyle();
                }
                else
                    header = new DataSheetHeaderCell();


                cell.Text = header.Text;
                hrow0.Cells.Add(cell);

                var footer = dscol.Footer;
                if (footer != null)
                {
                    if (frow == null)
                    {
                        frow = new TableRow();
                        if (colindex > 0)
                        {
                            TableCell ph = new TableCell();
                            ph.ColSpan = colindex;
                            frow.Cells.Add(ph);
                        }
                    }

                    if (footer.Aggregation == AggregationFunction.None)
                    {
                        cell = new TableCell();
                        cell.Text = footer.Text;
                    }
                    else
                    {
                        cell = new AggregatableCell(footer);
                        aggcells.Add((AggregatableCell)cell);
                    }

                    cell.Style = dscol.GetFooterStyle();
                    frow.Cells.Add(cell);
                    fcell = cell;
                }
                else
                {
                    if (fcell != null)
                        fcell.ColSpan++;
                }

                table.Columns.Add(new TableColumn { Width = dscol.Width });
                
            }

            if (hcount > 0)
            {
                if (gcount > 0)
                {
                    string g0 = string.Empty;
                    int ci = 0;

                    hrow1 = new TableRow();
                    TableCell gc = null;

                    foreach (var col in Columns)
                    {
                        var h = col.Header;
                        var cell = hrow0.Cells[ci];
                        string g1;

                        if (h != null) g1 = h.Group ?? string.Empty;
                        else g1 = string.Empty;

                        if (g1.Length > 0)
                        {
                            
                            if (gc != null && g1 == g0)
                            {
                                gc.ColSpan++;
                            }
                            else
                            {
                                gc = new TableCell();
                                gc.Text = g1;
                                gc.Style = cell.Style;
                                hrow0.Cells.Insert(ci, gc);
                                ci++;
                            }

                            hrow0.Cells.RemoveAt(ci);
                            ci--;
                            hrow1.Cells.Add(cell);
                        }
                        else
                        {
                            cell.RowSpan = 2;
                        }

                        g0 = g1;
                        ci++;
                    }

                    table.Rows.Add(hrow0);
                    table.Rows.Add(hrow1);
                }
                else
                    table.Rows.Add(hrow0);
            }

            AddDataRowsToTable(table, aggcells);

            if (frow != null)
                table.Rows.Add(frow);
        }

        private void AddDataRowsToTable(Table table, List<AggregatableCell> aggcells)
        {
            IEnumerable coll = GetDataSource();
            int count = 0;
            
            foreach (object dataItem in coll)
            {
                TableRow row = CreateTableRow(count, dataItem);

                foreach (var ac in aggcells)
                    ac.Aggregate(dataItem);

                table.Rows.Add(row);
                count++;
            }

            while (_preferredRowCount > count)
            {
                TableRow row = CreateTableRow(count, null);

                table.Rows.Add(row);
                count++;

            }

            foreach (var ac in aggcells)
                ac.CompleteAggregate();
        }

        private TableRow CreateTableRow(int rowIndex, object dataItem)
        {
            TableRow row = new TableRow();

            foreach (var col in Columns)
            {
                TableCell cell = new TableCell();
                cell.Text = col.Body.GetText(dataItem, rowIndex);
                cell.DisableEval = true;
                cell.Style = col.Body.Style;

                row.Cells.Add(cell);
            }
            return row;
        }

        protected internal override SizeF GetClientSize(RenderingContext context)
        {
            return GetTable(context).GetClientSize(context);
        }
    }

    internal class AggregatableCell : TableCell
    {
        AggregationContext _context;

        public AggregatableCell(DataSheetFooterCell dsfc)
        {
            _dsfc = dsfc;

            string dataMember = dsfc.DataMember;
            AggregationFunction function = dsfc.Aggregation;

            if (dataMember == null)
            {
                if (function != AggregationFunction.CountAll && function != AggregationFunction.CountValues)
                    dataMember = dsfc.Column.Body.DataMember;
            }

            _context = AggregationContext.Create(function, dataMember);
        }

        DataSheetFooterCell _dsfc;

        internal void Aggregate(object dataItem)
        {
            if (_context != null)
                _context.Aggregate(dataItem);
        }

        internal void CompleteAggregate()
        {
            if (_context != null)
            {
                object result = _context.Complete();
                var body = _dsfc.Column.Body;

                Text = Utils.Formatting.Format(result, _dsfc.Format ?? body.Format, _dsfc.NullText ?? body.NullText, _context.Property);
            }
        }
    }
}
